﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace NeedForSmash
{
    class Element : DrawableGameComponent
    {
        private Game m_game;

        private Model m_model;
        public Model Model
        {
            get
            {
                return m_model;
            }
        }

        private Vector3 m_position;
        public Vector3 Position
        {
            get
            {
                return m_position;
            }
            set
            {
                m_position = value;
            }
        }

        public Vector3 m_velocity;

        private bool m_isStatic;
        public bool IsStatic
        {
            get
            {
                return m_isStatic;
            }
            set
            {
                m_isStatic = value;
            }
        }

        private Matrix[] m_transforms;
        public Matrix[] Transforms
        {
            get
            {
                return m_transforms;
            }
            set
            {
                m_transforms = value;
            }
        }

        private BoundingBox m_boundingBox;
        public BoundingBox BoundingBox
        {
            get
            {
                return m_boundingBox;
            }
            set
            {
                m_boundingBox = value;
            }
        }

        private string m_assetName;

        public Element(Game game, string assetName, Vector3 position)
            : base(game)
        {
            m_game = game;
            m_position = position;
            m_assetName = assetName;
            m_velocity = new Vector3(0, 0, 0);
            m_isStatic = false;
        }

        public override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            m_model = m_game.Content.Load<Model>(m_assetName);
            m_transforms = new Matrix[m_model.Bones.Count];
            m_model.CopyAbsoluteBoneTransformsTo(m_transforms);
            m_boundingBox = UpdateBoundingBox(m_model, Matrix.Identity);
            base.LoadContent();
        }

        public override void Draw(GameTime gameTime)
        {
            foreach (ModelMesh mesh in m_model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();

                    effect.View = m_game.Camera.ViewMatrix;
                    effect.Projection = m_game.Camera.ProjectionMatrix;
                    effect.World = m_transforms[mesh.ParentBone.Index] *
                        Matrix.CreateScale(1.0f, 1.0f, 1.0f) *
                        Matrix.CreateRotationX(0.0f) *
                        Matrix.CreateRotationY(0.0f) *
                        Matrix.CreateRotationZ(0.0f) *
                        Matrix.CreateTranslation(m_position);
                }
                mesh.Draw();
            }
#if DEBUG
            DrawBox(m_boundingBox);
#endif
            base.Draw(gameTime);
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        private BoundingBox CalculateBoundingBox()
        {
            // Create variables to hold min and max xyz values for the model. Initialise them to extremes
            Vector3 modelMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            Vector3 modelMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

            foreach (ModelMesh mesh in m_model.Meshes)
            {
                //Create variables to hold min and max xyz values for the mesh. Initialise them to extremes
                Vector3 meshMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
                Vector3 meshMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

                // There may be multiple parts in a mesh (different materials etc.) so loop through each
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    // The stride is how big, in bytes, one vertex is in the vertex buffer
                    // We have to use this as we do not know the make up of the vertex
                    int stride = part.VertexBuffer.VertexDeclaration.VertexStride;

                    byte[] vertexData = new byte[stride * part.NumVertices];
                    part.VertexBuffer.GetData(part.VertexOffset * stride, vertexData, 0, part.NumVertices, 1); // fixed 13/4/11

                    // Find minimum and maximum xyz values for this mesh part
                    // We know the position will always be the first 3 float values of the vertex data
                    Vector3 vertPosition = new Vector3();
                    for (int ndx = 0; ndx < vertexData.Length; ndx += stride)
                    {
                        vertPosition.X = BitConverter.ToSingle(vertexData, ndx);
                        vertPosition.Y = BitConverter.ToSingle(vertexData, ndx + sizeof(float));
                        vertPosition.Z = BitConverter.ToSingle(vertexData, ndx + sizeof(float) * 2);

                        // update our running values from this vertex
                        meshMin = Vector3.Min(meshMin, vertPosition);
                        meshMax = Vector3.Max(meshMax, vertPosition);
                    }
                }

                // transform by mesh bone transforms
                meshMin = Vector3.Transform(meshMin, m_transforms[mesh.ParentBone.Index]);
                meshMax = Vector3.Transform(meshMax, m_transforms[mesh.ParentBone.Index]);

                // Expand model extents by the ones from this mesh
                modelMin = Vector3.Min(modelMin, meshMin);
                modelMax = Vector3.Max(modelMax, meshMax);
            }

            modelMin += m_position;
            modelMax += m_position;

            // Create and return the model bounding box
            return new BoundingBox(modelMin, modelMax);


        }

        protected BoundingBox UpdateBoundingBox(Model model, Matrix worldTransform)
        {
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            // For each mesh of the model
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    // Vertex buffer parameters
                    int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                    int vertexBufferSize = meshPart.NumVertices * vertexStride;

                    // Get vertex data as float
                    float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                    meshPart.VertexBuffer.GetData<float>(vertexData);

                    // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                    for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                    {
                        Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), worldTransform);

                        min = Vector3.Min(min, transformedPosition);
                        max = Vector3.Max(max, transformedPosition);
                    }
                }
            }

            min += m_position;
            max += m_position;

            // Create and return bounding box
            return new BoundingBox(min, max);
        }

        public void Move(Vector3 v)
        {
            m_position += v;
            m_boundingBox.Min += v;
            m_boundingBox.Max += v;
        }

        public void StopMove()
        {
            m_velocity = new Vector3(0, 0, 0);
        }

        VertexPositionColor[] points;
        short[] index;
        BasicEffect lineShader;

        protected void DrawBox(BoundingBox box)
        {
            BuildBox(box, Color.White);
            DrawLines(12);
        }

        protected void BuildBox(BoundingBox box, Color lineColor)
        {
            points = new VertexPositionColor[8];

            Vector3[] corners = box.GetCorners();

            points[0] = new VertexPositionColor(corners[1], lineColor); // Front Top Right
            points[1] = new VertexPositionColor(corners[0], lineColor); // Front Top Left
            points[2] = new VertexPositionColor(corners[2], lineColor); // Front Bottom Right
            points[3] = new VertexPositionColor(corners[3], lineColor); // Front Bottom Left
            points[4] = new VertexPositionColor(corners[5], lineColor); // Back Top Right
            points[5] = new VertexPositionColor(corners[4], lineColor); // Back Top Left
            points[6] = new VertexPositionColor(corners[6], lineColor); // Back Bottom Right
            points[7] = new VertexPositionColor(corners[7], lineColor); // Bakc Bottom Left

            index = new short[] {
	            0, 1, 0, 2, 1, 3, 2, 3,
	            4, 5, 4, 6, 5, 7, 6, 7,
	            0, 4, 1, 5, 2, 6, 3, 7
                };
        }

        protected void DrawLines(int primativeCount)
        {
            if (lineShader == null)
                lineShader = new BasicEffect(GraphicsDevice);

            lineShader.World = Matrix.Identity;
            lineShader.View = m_game.Camera.ViewMatrix;
            lineShader.Projection = m_game.Camera.ProjectionMatrix;
            lineShader.VertexColorEnabled = true;

            lineShader.CurrentTechnique.Passes[0].Apply();
            GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, points, 0, points.Length, index, 0, primativeCount);
        }
    }
}
